package services

import (
	"errors"
	"fmt"
	"go.uber.org/zap"
	"strconv"
	"strings"
	"time"
	"yunwei/go_api/application/database"
	"yunwei/go_api/application/model"
	"yunwei/go_api/application/utils"
)

// 定义job结构体
type Job struct {
	// 任务id
	Id int
	// 任务名
	Name string
	// 直接拿task表
	Task *model.Task
	// 用于任务日志的字段
	// 1.任务输出
	// 2.错误提示
	// 3.系统错误信息
	// 4.是否超时
	RunFunc func(duration time.Duration) (string, string, error, bool)
	// 任务状态
	Status int
	// 是否允许并发 true   false
	Concurrent bool
}

// 实现cron中的Job接口的Run()方法
// 1.判断是否允许并发执行
// 2.修改任务状态
// 3.执行任务，需要设置超时时间，若超时则返回失败日志
// 4.把日志存数据库
// 5.若任务需要邮件提示错误，给指定管理员发邮件
// 6.更新上次下次时间即可
func (job *Job) Run() {
	// 状态
	// -1-删除 0-停用 1-启用 2-待审核 3-审核不通过
	if !job.Concurrent && job.Status > 0 {
		zap.S().Error("任务不允许并发执行")
		return
	}
	// 任务异常处理
	defer func() {
		if err := recover(); err != nil {
			zap.S().Error(err)
		}
	}()
	// 修改执行任务状态
	zap.S().Infof(fmt.Sprintf("开始执行任务：%d", job.Id))
	job.Status++
	defer func() {
		job.Status--
	}()
	start := time.Now()
	// 设置任务超时时间
	timeout := time.Duration(30 * time.Second)
	if job.Task.Timeout > 0 {
		timeout = time.Duration(job.Task.Timeout)
	}
	// 任务执行
	// 1.任务输出
	// 2.错误提示
	// 3.系统错误信息
	// 4.是否超时
	cmdOut, cmdErr, err, isTimeout := job.RunFunc(timeout)
	fmt.Println("cmdOut, cmdErr, err, isTimeout::::", cmdOut, cmdErr, err, isTimeout)
	// 构建task日志对象，用于存数据库
	log := new(model.TaskLog)
	log.TaskId = job.Id
	log.Output = cmdOut
	log.Error = cmdErr

	log.ProcessTime = 0
	log.CreateTime = start.Unix()
	// 判断任务是否超时
	if isTimeout {
		// 任务超时
		log.Status = model.TSAK_TIMEOUT
		log.Error = fmt.Sprintf("任务超时")
	} else if err != nil {
		// 任务出错
		log.Status = model.TASK_ERROR
		log.Error = fmt.Sprintf("任务出错")
	}
	// 异常任务，发警报邮件相关
	// 0-正常 -1-错误 -2-超时
	TextStatus := []string{
		"<font color='red'>超时</font>",
		"<font color='red'>错误</font>",
		"<font color='green'>正常</font>",
	}
	// 用于TextStatus切片的取值
	email_status_index := log.Status + 2
	// 判断任务执行失败，且失败需要邮件通知
	if log.Status < 0 && job.Task.IsNotify == 1 {
		// 判断需要通知的人的id不为空
		if job.Task.NotifyUserIds != "0" && job.Task.NotifyUserIds != "" {
			fmt.Println("发送报警邮件")
			// 收邮件的管理员的email
			toEmail := ""
			// 根据NotifyUserIds查询接收邮件的所有的管理员
			toEmail_slice := []string{"yuan_xiang2020@163.com"}
			for _, to := range toEmail_slice {
				subject := fmt.Sprintf("CronJob定时任务异常：%s", job.Task.TaskName)
				body := fmt.Sprintf(
					`
					<html>
						<body>
						Hello，定时任务出错了：
						<p style="font-size:16px;">任务执行详情：</p>
						<p style="display:block; padding:10px; background:#efefef;border:1px solid #e4e4e4">
						任务 ID：%d<br/>
						任务名称：%s<br/>
						执行时间：%s<br/>
						执行耗时：%f秒<br/>
						执行状态：%s
						</p>
						<br/>
						<p>-----------------------------------------------------------------<br />
						本邮件由CronJob定时系统自动发出，请勿回复<br />
						如果要取消邮件通知，请登录到系统进行设置<br />
						</p>
						</body>
					</html>
						`, job.Task.Id,
					job.Task.TaskName,
					time.Now().String(),
					float64(log.ProcessTime)/1000,
					TextStatus[email_status_index])
				mailtype := "text/html"
				ok := utils.SendToChan(to, subject, body, mailtype)
				if !ok {
					fmt.Println("发送邮件错误", toEmail)
				}
			}
		}
	}
	// 将日志插入到数据库表
	err = model.TaskLogAdd(log)
	if err != nil {
		zap.S().Error("插入task_log异常")
	}
	// 更改task数据库表记录
	job.Task.PrevTime = time.Now().Unix()
	job.Task.ExecuteTimes++
	err = job.Task.Update("PrevTime", "ExecuteTimes")
	if err != nil {
		zap.S().Error("更新task异常")
	}
}

func AllUserInfo(userIds string) []*model.User {
	Filters := make([]interface{}, 0)
	Filters = append(Filters, "status", 1)
	var notifyUserIds []int
	if userIds != "0" && userIds != "" {
		notifyUserIdStr := strings.Split(userIds, ",")
		for _, v := range notifyUserIdStr {
			i, _ := strconv.Atoi(v)
			notifyUserIds = append(notifyUserIds, i)
		}
		//Filters = append(Filters, "id__in", notifyUserIdStr)
	}
	notifyUserIdStr := strings.Split(userIds, ",")
	// 查询出所有符合条件的Admin
	// select * from pp_admin where status = 1 and id in(5,3,2)
	var users []*model.User
	fmt.Println("notifyUserIdStr:::", notifyUserIdStr)
	database.Orm.Where("id in (?)", []int{1}).Find(&users)
	fmt.Println("通知用户：", users)
	return users
}

// 实现管道中job创建、添加、查询、删除。。。供外部调用的方法
// 创建job
func NewJobFromTask(task *model.Task) (*Job, error) {
	// 严谨判断，例如任务id合规等
	// 1.本地执行的任务
	if task.ServerId == 0 {

	}
	// 2.远程执行的任务
	// 先根据serverId，把服务器找到
	var server model.Host
	fmt.Println("task.ServerId:::", task.ServerId)
	err := server.GetOneById(uint(task.ServerId))

	if err != nil {
		zap.S().Error("查询server失败")
	}
	if server.ID != 0 {

		// 组装job数据
		job := &Job{
			Id:   task.Id,
			Name: task.TaskName,
		}
		job.RunFunc = func(timeout time.Duration) (string, string, error, bool) {
			fmt.Println("执行远程命令...")

			cli := utils.NewSSH(server.IpAddr, server.Username, server.Password, server.PrivateKey, "password", int(server.Port))
			if err := cli.Connect(); err != nil {
				fmt.Println("err:", err)
				return "", "连接主机失败！", err, false
			}

			defer func(cli *utils.SSH) {
				_ = cli.Client.Close()
				_ = cli.Session.Close()
			}(cli)

			output, err := cli.Run(task.Command)
			if err != nil {
				return "", "执行命令失败！", err, false
			}

			return output, "", err, false

		}
		job.Task = task
		job.Concurrent = task.Concurrent == 1

		return job, nil

	}
	return nil, errors.New("job创建失败")
}

var MainCron *Cron

// job添加
func AddJob(spec string, job Job) bool {

	// 1.加锁
	// 2.管道里查一下
	if GetEntryById(job.Id) != nil {
		return false
	}
	// 添加job到管道
	err := MainCron.AddJob(spec, job)
	if err != nil {
		zap.S().Error("添加job失败")
		return false
	}
	return true
}

// 根据id查管道任务
func GetEntryById(id int) *Entry {
	entries := MainCron.Entries()
	for _, e := range entries {

		if e.Job.Id == id {
			return e
		}
	}
	return nil
}

// 查询即将执行的几个任务，首页要执行的job
func GetEntries(size int) []*Entry {
	entry := MainCron.Entries()
	if len(entry) > size {
		return entry[:size]
	}
	return entry
}

// 删除
func RemoveJob(id int) {
	MainCron.RemoveJob(func(e *Entry) bool {

		return e.Job.Id == id

	})
}
